package concurrent;


import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

/**
 * 场景：秒杀活动，数量有限，访问量巨大，为了防止系统宕机，需要做限流处理
 * 国庆期间，一般的旅游景点人口太多，采用排队方式做限流处理
 * 医院看病通过发放排队号的方式来做限流处理。
 * 常见限流算法：通过控制最大并发数来进行限流
 * 使用漏桶算法来进行限流
 * 使用令牌桶算法来进行限流
 *
 * @author jack.wu
 * @since 2019/12/24 11:39
 */
public class LimitFlow {
}

/**
 * 并发控制的，通俗解释：一大波人去商场购物，必须经过一个门口，门口有个门卫，兜里面有指定数量的门禁卡，
 * 来的人先去门卫那边拿取门禁卡，拿到卡的人才可以刷卡进入商场，拿不到的可以继续等待。
 * 进去的人出来之后会把卡归还给门卫，门卫可以把归还来的卡继续发放给其他排队的顾客使用
 * JUC中提供了这样的工具类：Semaphore
 */
class ControllingConcurrency {

    static Semaphore semaphore = new Semaphore(5);

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                boolean flag;
                try {
                    flag = semaphore.tryAcquire(100, TimeUnit.MILLISECONDS);
                    if (flag) {
                        // 休眠2秒，模拟下单操作
                        System.out.println(Thread.currentThread().getName() + "，尝试下单中。。。");
                        TimeUnit.SECONDS.sleep(2);
                    } else {
                        System.out.println(Thread.currentThread().getName() + "，秒杀失败，请稍后重试。。。");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }).start();
        }
    }
}

/**
 * 漏桶算法：水（请求）先进入到漏桶里，漏桶以一定的速度出水，
 * 当水流入速度过大会直接溢出，可以看出漏桶算法能强行限制数据的传输速率。
 */
class LeakyBucket {

    public static class BucketLimit {
        static AtomicInteger threadNum = new AtomicInteger(1);

        /**
         * 容量
         */
        private int capacity;

        /**
         * 流速
         */
        private int flowRate;

        /**
         * 流速时间单位
         */
        private TimeUnit flowRateUnit;
        private BlockingQueue<Node> queue;
        /**
         * 漏桶流出的任务时间间隔（纳秒）
         */
        private long flowRateNanosTime;

        public BucketLimit(int capacity, int flowRate, TimeUnit flowRateUnit) {
            this.capacity = capacity;
            this.flowRate = flowRate;
            this.flowRateUnit = flowRateUnit;
            this.bucketThreadWork();
        }

        /**
         * 漏桶线程
         */
        private void bucketThreadWork() {
            this.queue = new ArrayBlockingQueue<>(capacity);
            this.flowRateNanosTime = flowRateUnit.toNanos(1) / flowRate;
            Thread thread = new Thread(this::bucketWork);
            thread.setName("漏桶线程-" + threadNum.getAndIncrement());
            thread.start();
        }

        /**
         * 漏桶线程开始工作
         */
        private void bucketWork() {
            while (true) {
                Node node = this.queue.poll();
                if (Objects.nonNull(node)) {
                    // 唤醒任务线程
                    LockSupport.unpark(node.thread);
                }
                // 休眠flowRateNanosTime
                LockSupport.parkNanos(this.flowRateNanosTime);
            }
        }

        /**
         * 返回一个漏桶
         *
         * @param capacity     容量
         * @param flowRate     流速
         * @param flowRateUnit 流速时间单位
         * @return 漏桶
         */
        public static BucketLimit build(int capacity, int flowRate, TimeUnit flowRateUnit) {
            if (capacity < 0 || flowRate < 0) {
                throw new IllegalArgumentException();
            }
            return new BucketLimit(capacity, flowRate, flowRateUnit);
        }

        /**
         * 当前线程加入漏桶，成功加入返回true,否则返回false
         *
         * @return 状态
         */
        public boolean acquire() {
            Thread thread = Thread.currentThread();
            Node node = new Node(thread);
            if (this.queue.offer(node)) {
                LockSupport.park();
                return true;
            }
            return false;
        }

        class Node {
            private Thread thread;

            public Node(Thread thread) {
                this.thread = thread;
            }
        }
    }

    public static void main(String[] args) {
        BucketLimit bucketLimit = BucketLimit.build(10, 60, TimeUnit.MINUTES);
        for (int i = 0; i < 15; i++) {
            new Thread(() -> {
                boolean acquire = bucketLimit.acquire();
                System.out.println(System.currentTimeMillis() + " " + acquire);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

/**
 * 令牌桶算法的原理是系统以恒定的速率产生令牌，然后把令牌放到令牌桶中，令牌桶有一个容量，
 * 当令牌桶满了的时候，再向其中放令牌，那么多余的令牌会被丢弃；
 * 当想要处理一个请求的时候，需要从令牌桶中取出一个令牌，如果此时令牌桶中没有令牌，那么则拒绝该请求。
 * 从原理上看，令牌桶算法和漏桶算法是相反的，一个“进水”，一个是“漏水”。
 * 这种算法可以应对突发程度的请求，因此比漏桶算法好。
 *
 * Google开源工具包Guava提供了限流工具类RateLimiter
 */
class TokenBucket {

}
